<template>
  <FormModel
    v-bind="getBindValue"
    :class="getFormClass"
    ref="formElRef"
    :model="formModel"
    @keypress.enter="handleEnterPress"
  >
    <Row v-bind="getRow">
      <slot name="formHeader"></slot>
      <FormItem
        v-for="schema in getSchema"
        :key="schema.field"
        :isAdvanced="fieldsIsAdvancedMap[schema.field]"
        :tableAction="tableAction"
        :formActionType="formActionType"
        :schema="schema"
        :formProps="getProps"
        :allDefaultValues="defaultValueRef"
        :formModel="formModel"
        :setFormModel="setFormModel"
      >
        <template #[item]="data" v-for="item in Object.keys(slots)">
          <slot :name="item" v-bind="data || {}"></slot>
        </template>
      </FormItem>

      <FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
        <template #[item]="data" v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']">
          <slot :name="item" v-bind="data || {}"></slot>
        </template>
      </FormAction>
      <div v-show="false">
        {{ formChange }}
      </div>

      <slot name="formFooter"></slot>
    </Row>
  </FormModel>
</template>
<script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form'
import type { AdvanceState } from './types/hooks'
import type { Ref } from 'vue'

import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue'
import { Row, FormModel } from 'ant-design-vue'
import FormItem from './components/FormItem.vue'
import FormAction from './components/FormAction.vue'

import { dateItemType } from './helper'
import { dateUtil } from '/@/utils/dateUtil'

// import { cloneDeep } from 'lodash-es';
import { deepMerge } from '/@/utils'

import { useFormValues } from './hooks/useFormValues'
import useAdvanced from './hooks/useAdvanced'
import { useFormEvents } from './hooks/useFormEvents'
import { createFormContext } from './hooks/useFormContext'
import { useAutoFocus } from './hooks/useAutoFocus'
import { useModalContext } from '/@/vbenComponents/Modal'
import { useDebounceFn } from '@vueuse/core'

import { basicProps } from './props'
// import { useDesign } from '/@/hooks/web/useDesign';
import { cloneDeep } from 'lodash-es'
import { isFunction, isArray } from '/@/utils/is'
import { buildUUID } from '@/utils/uuid'

export default defineComponent({
  name: 'BasicForm',
  components: { FormItem, Row, FormAction, FormModel },
  props: basicProps,
  emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
  setup(props, { emit, attrs, slots }) {
    const formModel = reactive<Recordable>({})
    const formChange = ref('0')
    const modalFn = useModalContext()

    const advanceState = reactive<AdvanceState>({
      isAdvanced: true,
      hideAdvanceBtn: false,
      isLoad: false,
      actionSpan: 6,
    })

    const defaultValueRef = ref<Recordable>({})
    const isInitedDefaultRef = ref(false)
    const propsRef = ref<Partial<FormProps>>({})
    const schemaRef = ref<Nullable<FormSchema[]>>(null)
    const formElRef = ref<Nullable<FormActionType>>(null)

    // const { prefixCls } = useDesign('basic-form');
    const prefixCls = 'basic-form'

    // Get the basic configuration of the form
    const getProps = computed((): FormProps => {
      return { ...props, ...unref(propsRef) } as FormProps
    })

    const getFormClass = computed(() => {
      return [
        prefixCls,
        {
          [`${prefixCls}--compact`]: unref(getProps).compact,
        },
      ]
    })

    // Get uniform row style and Row configuration for the entire form
    const getRow = computed((): Recordable => {
      const { baseRowStyle = {}, rowProps } = unref(getProps)
      return {
        style: baseRowStyle,
        ...rowProps,
      }
    })

    const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable))
    const getSchema = computed((): FormSchema[] => {
      const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
      for (const schema of schemas) {
        const { defaultValue, component, isHandleDateDefaultValue = true } = schema
        // handle date type
        if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
          if (!Array.isArray(defaultValue)) {
            schema.defaultValue = dateUtil(defaultValue)
          } else {
            const def: any[] = []
            defaultValue.forEach((item) => {
              def.push(dateUtil(item))
            })
            schema.defaultValue = def
          }
        }
      }
      if (unref(getProps).showAdvancedButton) {
        return cloneDeep(schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[])
      } else {
        return cloneDeep(schemas as FormSchema[])
      }
    })

    const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
      advanceState,
      emit,
      getProps,
      getSchema,
      formModel,
      defaultValueRef,
    })

    const { handleFormValues, initDefault } = useFormValues({
      getProps,
      defaultValueRef,
      getSchema,
      formModel,
    })

    useAutoFocus({
      getSchema,
      getProps,
      isInitedDefault: isInitedDefaultRef,
      formElRef: formElRef as Ref<FormActionType>,
    })
    /**
     * 重置需要特殊处理
     */
    const handleReset = (values) => {
      Object.keys(formModel).forEach((key) => {
        formModel[key] = values[key]
      })
      formChange.value = buildUUID()
    }

    const {
      handleSubmit,
      setFieldsValue,
      clearValidate,
      validate,
      validateFields,
      getFieldsValue,
      updateSchema,
      resetSchema,
      appendSchemaByField,
      removeSchemaByField,
      resetFields,
      scrollToField,
    } = useFormEvents({
      emit,
      getProps,
      formModel,
      getSchema,
      defaultValueRef,
      formElRef: formElRef as Ref<FormActionType>,
      schemaRef: schemaRef as Ref<FormSchema[]>,
      handleFormValues,
      handleReset,
    })

    createFormContext({
      resetAction: resetFields,
      submitAction: handleSubmit,
    })

    watch(
      () => unref(getProps).model,
      () => {
        const { model } = unref(getProps)
        if (!model) return
        setFieldsValue(model)
      },
      {
        immediate: true,
      }
    )

    watch(
      () => unref(getProps).schemas,
      (schemas) => {
        resetSchema(schemas ?? [])
      }
    )

    watch(
      () => getSchema.value,
      (schema) => {
        nextTick(() => {
          //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
          modalFn?.redoModalHeight?.()
        })
        if (unref(isInitedDefaultRef)) {
          return
        }
        if (schema?.length) {
          initDefault()
          isInitedDefaultRef.value = true
        }
      }
    )

    watch(
      () => [formModel, formChange],
      useDebounceFn(() => {
        unref(getProps).submitOnChange && handleSubmit()
      }, 300),
      { deep: true }
    )

    async function setProps(formProps: Partial<FormProps>): Promise<void> {
      propsRef.value = deepMerge(unref(propsRef) || {}, formProps)
    }

    function setFormModel(key: string, value: any, schema: FormSchema) {
      formModel[key] = value
      formChange.value = buildUUID()
      const { validateTrigger } = unref(getBindValue)
      if (isFunction(schema.dynamicRules) || isArray(schema.rules)) {
        return
      }
      if (!validateTrigger || validateTrigger === 'change') {
        validateFields([key]).catch((_) => {})
      }
      emit('field-value-change', key, value)
    }

    function handleEnterPress(e: KeyboardEvent) {
      const { autoSubmitOnEnter } = unref(getProps)
      if (!autoSubmitOnEnter) return
      if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
        const target: HTMLElement = e.target as HTMLElement
        if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
          handleSubmit()
        }
      }
    }

    const formActionType: Partial<FormActionType> = {
      getFieldsValue,
      setFieldsValue,
      resetFields,
      updateSchema,
      resetSchema,
      setProps,
      removeSchemaByField,
      appendSchemaByField,
      clearValidate,
      validateFields,
      validate,
      submit: handleSubmit,
      scrollToField: scrollToField,
    }

    onMounted(() => {
      initDefault()
      emit('register', formActionType)
    })
    return {
      slots,
      formChange,
      getBindValue,
      handleToggleAdvanced,
      handleEnterPress,
      formModel,
      defaultValueRef,
      advanceState,
      getRow,
      getProps,
      formElRef,
      getSchema,
      formActionType: formActionType as any,
      setFormModel,
      getFormClass,
      getFormActionBindProps: computed((): Recordable => ({ ...getProps.value, ...advanceState })),
      fieldsIsAdvancedMap,
      ...formActionType,
    }
  },
})
</script>
<style lang="less">
// @prefix-cls: ~'@{namespace}-basic-form';

.basic-form {
  .ant-col {
    .ant-row {
      display: flex;
      flex-flow: row wrap;
    }
  }

  .ant-form-item {
    &-label label::after {
      margin: 0 6px 0 2px;
    }

    &-with-help {
      margin-bottom: 0;
    }

    &:not(.ant-form-item-with-help) {
      margin-bottom: 20px;
    }

    &.suffix-item {
      .ant-form-item-children {
        display: flex;
      }

      .ant-form-item-control {
        // margin-top: 4px;
      }

      .suffix {
        display: inline-flex;
        padding-left: 6px;
        margin-top: 1px;
        line-height: 1;
        align-items: center;
      }
    }
  }

  .ant-form-explain {
    font-size: 14px;
  }

  &--compact {
    .ant-form-item {
      margin-bottom: 8px !important;
    }
  }
}
span {
  .suffix {
    line-height: 1;
  }
}
</style>
